// >	Increment the data pointer by one (to point to the next cell to the right).
// <	Decrement the data pointer by one (to point to the next cell to the left).
// +	Increment the byte at the data pointer by one.
// -	Decrement the byte at the data pointer by one.
// .	Output the byte at the data pointer.
// ,	Accept one byte of input, storing its value in the byte at the data pointer.
// [	If the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching ] command.
// ]	If the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching [ command.[
#[derive(Debug, PartialEq)]
pub enum OpCode {
    SHR = 0x3E,
    SHL = 0x3C,
    ADD = 0x2B,
    SUB = 0x2D,
    PUTCHAR = 0x2E,
    GETCHAR = 0x2C,
    LB = 0x5B,
    RB = 0x5D,
}

impl From<u8> for OpCode {
    fn from(u: u8) -> Self {
        match u {
            0x3E => OpCode::SHR,
            0x3C => OpCode::SHL,
            0x2B => OpCode::ADD,
            0x2D => OpCode::SUB,
            0x2E => OpCode::PUTCHAR,
            0x2C => OpCode::GETCHAR,
            0x5B => OpCode::LB,
            0x5D => OpCode::RB,
            _ => unimplemented!(),
        }
    }
}
#[derive(Debug, PartialEq)]
pub struct Code {
    pub instrs: Vec<OpCode>,
    pub jtable: std::collections::HashMap<usize, usize>,
}

impl Code {
    pub fn from(data: Vec<u8>) -> Result<Self, Box<dyn std::error::Error>> {
        let dict: Vec<u8> = vec![
            OpCode::SHR as u8,
            OpCode::SHL as u8,
            OpCode::ADD as u8,
            OpCode::SUB as u8,
            OpCode::PUTCHAR as u8,
            OpCode::GETCHAR as u8,
            OpCode::LB as u8,
            OpCode::RB as u8,
        ];
        let instrs: Vec<OpCode> = data
            .iter()
            .filter(|x| dict.contains(x))
            .map(|x| OpCode::from(*x))
            .collect();
        // TODO构建跳转表
        let mut jtable = std::collections::HashMap::new();
        let mut stack = Vec::new();

        for (pos, op) in instrs.iter().enumerate() {
            match op {
                OpCode::LB => stack.push(pos),
                OpCode::RB => {
                    let matching_pos = stack.pop().ok_or("Unmatched ']'")?;
                    jtable.insert(matching_pos, pos);
                    jtable.insert(pos, matching_pos);
                }
                _ => {}
            }
        }

        if !stack.is_empty() {
            return Err("Unmatched '['".into());
        }
        // todo 先这样返回到起
        Ok(Self { instrs, jtable })
    }
}
